﻿using System;
using System.Globalization;
using System.Xml;
using System.Xml.Linq;

namespace Elentok.Common.Helpers
{
  /// <summary>
  /// This method contains various extensions to XmlElement and XElement classes
  /// </summary>
  public static class XmlHelper
  {
    #region Get XElement Attributes (by type)
    /// <summary>
    /// Returns the value of the string attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static string GetStringAttribute(this XElement element, string attributeName, string defaultValue)
    {
      XAttribute attrib = element.Attribute(attributeName);
      if (attrib == null)
        return defaultValue;

      return attrib.Value;
    }

    /// <summary>
    /// Returns the value of the string attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static string GetStringAttribute(this XElement element, string attributeName)
    {
      return element.GetRequiredAttribute(attributeName).Value;
    }

    /// <summary>
    /// Returns the value of the boolean attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static bool GetBooleanAttribute(this XElement element, string attributeName, bool defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return bool.Parse(value);
    }

    /// <summary>
    /// Returns the value of the boolean attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static bool GetBooleanAttribute(this XElement element, string attributeName)
    {
      return bool.Parse(element.GetRequiredAttribute(attributeName).Value);
    }

    /// <summary>
    /// Returns the value of the integer attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static int GetNumberAttribute(this XElement element, string attributeName, int defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return int.Parse(value, CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns the value of the integer attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static int GetNumberAttribute(this XElement element, string attributeName)
    {
      return int.Parse(element.GetRequiredAttribute(attributeName).Value,
        CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns the value of the datetime attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static DateTime? GetDateTimeAttribute(this XElement element, string attributeName, DateTime? defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return DateTime.Parse(value, CultureInfo.InvariantCulture);
    }

    /// <summary>
    /// Returns the value of the datetime attribute (throws an exception if it doesn't exist)
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static DateTime GetDateTimeAttribute(this XElement element, string attributeName)
    {
      return DateTime.Parse(element.GetRequiredAttribute(attributeName).Value, CultureInfo.InvariantCulture);
    }

    /// <summary>
    /// Returns the value of the enum attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static TEnum GetEnumAttribute<TEnum>(this XElement element, string attributeName, TEnum defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    /// <summary>
    /// Returns the value of the enum attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static TEnum GetEnumAttribute<TEnum>(this XElement element, string attributeName)
    {
      XAttribute attrib = element.GetRequiredAttribute(attributeName);
      return (TEnum)Enum.Parse(typeof(TEnum), attrib.Value);
    }

    /// <summary>
    /// Returns the attribute with the give name (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    /// <exception cref="MissingXmlAttributeException"></exception>
    public static XAttribute GetRequiredAttribute(this XElement element, string attributeName)
    {
      XAttribute attrib = element.Attribute(attributeName);
      if (attrib == null)
        throw new MissingXmlAttributeException(element.Name.LocalName, attributeName);

      return attrib;
    }
    #endregion

    #region Get XmlElement Attributes (by type)
    /// <summary>
    /// Returns the value of the string attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static string GetStringAttribute(this XmlElement element, string attributeName, string defaultValue)
    {
      if (element.HasAttribute(attributeName))
        return element.GetAttribute(attributeName);

      return defaultValue;
    }

    /// <summary>
    /// Returns the value of the string attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static string GetStringAttribute(this XmlElement element, string attributeName)
    {
      if (!element.HasAttribute(attributeName))
        throw new MissingXmlAttributeException(element.Name, attributeName);

      return element.GetAttribute(attributeName);
    }

    /// <summary>
    /// Returns the value of the boolean attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static bool GetBooleanAttribute(this XmlElement element, string attributeName, bool defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return bool.Parse(value);
    }

    /// <summary>
    /// Returns the value of the boolean attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static bool GetBooleanAttribute(this XmlElement element, string attributeName)
    {
      return bool.Parse(element.GetStringAttribute(attributeName));
    }

    /// <summary>
    /// Returns the value of the integer attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static int GetNumberAttribute(this XmlElement element, string attributeName, int defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return int.Parse(value, CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns the value of the integer attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static int GetNumberAttribute(this XmlElement element, string attributeName)
    {
      return int.Parse(element.GetStringAttribute(attributeName),
        CultureInfo.CurrentCulture);
    }

    /// <summary>
    /// Returns the value of the datetime attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static DateTime? GetDateTimeAttribute(this XmlElement element, string attributeName, DateTime? defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return DateTime.Parse(value, CultureInfo.InvariantCulture);
    }

    /// <summary>
    /// Returns the value of the datetime attribute 
    /// (throws an exception if the attribute doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static DateTime GetDateTimeAttribute(this XmlElement element, string attributeName)
    {
      return DateTime.Parse(element.GetStringAttribute(attributeName), CultureInfo.InvariantCulture);
    }


    /// <summary>
    /// Returns the value of the enum attribute (returns <paramref name="defaultValue"/>
    /// if the attribute doesn't exist)
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <param name="defaultValue"></param>
    /// <returns></returns>
    public static TEnum GetEnumAttribute<TEnum>(this XmlElement element, string attributeName, TEnum defaultValue)
    {
      string value = element.GetStringAttribute(attributeName, null);
      if (string.IsNullOrEmpty(value))
        return defaultValue;

      return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    /// <summary>
    /// Returns the value of the enum attribute (throws an exception if it doesn't exist)
    /// </summary>
    /// <typeparam name="TEnum"></typeparam>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    /// <returns></returns>
    public static TEnum GetEnumAttribute<TEnum>(this XmlElement element, string attributeName)
    {
      string value = element.GetStringAttribute(attributeName);
      return (TEnum)Enum.Parse(typeof(TEnum), value);
    }
    #endregion

    /// <summary>
    /// Removes an attribute from an xml element (if it doesn't exist, nothing happens)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="attributeName"></param>
    public static void RemoveAttribute(this XmlElement element, string attributeName)
    {
      if (element.HasAttribute(attributeName))
        element.RemoveAttribute(attributeName);
    }

    #region Create Child
    /// <summary>
    /// Gets the child element named <paramref name="elementName"/> (or creates it if it doesn't exist)
    /// </summary>
    /// <param name="element"></param>
    /// <param name="elementName"></param>
    /// <returns></returns>
    public static XElement GetOrCreateChildElement(this XElement element, string elementName)
    {
      XElement child = element.Element(elementName);
      if (child != null)
        return child;

      return element.CreateChild(elementName);
    }

    /// <summary>
    /// Creates a child node and adds it as the last childnode
    /// </summary>
    /// <param name="element"></param>
    /// <param name="elementName"></param>
    /// <returns></returns>
    public static XElement CreateChild(this XElement element, string elementName)
    {
      XElement child = new XElement(elementName);
      element.Add(child);
      return child;
    }
    #endregion
  }

  /// <summary>
  /// This exception is thrown when an XML element is missing an attribute
  /// </summary>
  [global::System.Serializable]
  public class MissingXmlAttributeException : Exception
  {
    //
    // For guidelines regarding the creation of new exception types, see
    //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
    // and
    //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
    //

    /// <summary>
    /// Initializes a new instance of the <see cref="MissingXmlAttributeException"/> class
    /// </summary>
    /// <param name="elementName"></param>
    /// <param name="attributeName"></param>
    public MissingXmlAttributeException(string elementName, string attributeName)
      : this(string.Format(CultureInfo.CurrentCulture, "Missing attribute '{0}' on element <{1}>", attributeName, elementName)) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="MissingXmlAttributeException"/> class
    /// </summary>
    public MissingXmlAttributeException() { }
    /// <summary>
    /// Initializes a new instance of the <see cref="MissingXmlAttributeException"/> class
    /// </summary>
    /// <param name="message"></param>
    public MissingXmlAttributeException(string message) : base(message) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="MissingXmlAttributeException"/> class
    /// </summary>
    /// <param name="message"></param>
    /// <param name="inner"></param>
    public MissingXmlAttributeException(string message, Exception inner) : base(message, inner) { }
    /// <summary>
    /// Initializes a new instance of the <see cref="MissingXmlAttributeException"/> class
    /// </summary>
    /// <param name="info"></param>
    /// <param name="context"></param>
    protected MissingXmlAttributeException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context)
      : base(info, context) { }
  }
}
